package ar.edu.unlam.tpfinal.persistence.dao.impl;

import java.sql.Timestamp;
import java.util.Date;
import java.util.List;

import javax.annotation.Resource;
import javax.persistence.PersistenceException;

import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Restrictions;
import org.springframework.transaction.annotation.Transactional;

import ar.edu.unlam.tpfinal.model.PersistentObject;
import ar.edu.unlam.tpfinal.persistence.dao.PersistentObjectDAO;
import ar.edu.unlam.tpfinal.persistence.exceptions.PersistenceExceptionWrapper;

@SuppressWarnings("unchecked")
@Transactional
public abstract class AbstractPersistenceDAOImpl<T extends PersistentObject> implements PersistentObjectDAO<T> {
	
	private Class<T> objectClass;
	
	@Resource
	private SessionFactory sessionFactory;
	
	public AbstractPersistenceDAOImpl(Class<T> clazz) {
        super();
        this.setObjectClass(clazz);
    }
	
	// DI de Spring
    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }
    
	protected Class<T> getObjectClass() {
        return objectClass;
    }

    protected void setObjectClass(Class<T> objectClass) {
        this.objectClass = objectClass;
    }
    
    private Session getSession() {
   		return sessionFactory.getCurrentSession();
    }
        
	@Override
	public void save(T abstractPersistentObject) {
		Session session = getSession();
		if(abstractPersistentObject.getCreationTimestamp() == null) {
			abstractPersistentObject.setCreationTimestamp(new Timestamp((new Date()).getTime()));
		}
		abstractPersistentObject.setModificationTimestamp(new Timestamp((new Date()).getTime()));
		try {
			session.saveOrUpdate(abstractPersistentObject);
		} catch (HibernateException ex) {
			throw new PersistenceExceptionWrapper("Error al tratar de guardar el objeto", ex, ex.getMessage());
		}
	}
	
	@Override
	public void saveAll(List<T> abstractPersistentObjectList) {
		for (T t : abstractPersistentObjectList) {
			this.save(t);
		}
	}
	
	@Override
	public void delete(T abstractPersistentObject) {
		Session session = getSession();
		try {
			session.delete(abstractPersistentObject);
		} catch (HibernateException ex) {
			throw new PersistenceExceptionWrapper("Error al tratar de borrar el objeto ", ex, ex.getMessage());
		}
	}
	
	@Override
	public void deleteAll(List<T> abstractPersistentObjectList) {
		for (T t : abstractPersistentObjectList) {
			this.delete(t);
		}
	}

	@Override
	public T findByOID(Long oid) {
		Session session = getSession();
		try {
			T t = (T) session.get(this.getObjectClass(), oid);
			return t;
		} catch (HibernateException ex) {
			throw new PersistenceExceptionWrapper("Error al tratar de obtener el objeto", ex, ex.getMessage());
		}
		
	}

	@Override
	public List<T> findAll() {
		try {
			Criteria criteria = getSession().createCriteria(this.getObjectClass().getName());
			List<T> list = (List<T>)criteria.list();
			return list;
		} catch (HibernateException ex) {
			throw new PersistenceExceptionWrapper("Error al crear Criteria", ex, ex.getMessage());
		}
	}

	@Override
	public List<T> getObjectsByProperties(String[] propertyNames, Object[] propertyValues) {
        List<T> lstObj = buildCriteriaFromProperties(propertyNames, propertyValues).list();
        return lstObj;
	}
	
	@Override
	public List<T> getObjectsByProperty(String propertyName, Object propertyValue) {
		String[] propertyNames = {propertyName};
		Object[] propertyValues = {propertyValue};
        List<T> lstObj = buildCriteriaFromProperties(propertyNames, propertyValues).list();
        return lstObj;
	}

	@Override
	public T getUniqueObjectByProperties(String[] propertyNames, Object[] propertyValues) {
		T t = (T) buildCriteriaFromProperties(propertyNames, propertyValues).uniqueResult();
		return t;
	}
	
	@Override
	public T getUniqueObjectByProperty(String propertyName,	Object propertyValue) {
		String[] propertyNames = {propertyName};
		Object[] propertyValues = {propertyValue};
		T t = (T) buildCriteriaFromProperties(propertyNames, propertyValues).uniqueResult();
		return t;
	}

	private Criteria buildCriteriaFromProperties(String[] propertyNames, Object[] propertyValues) {
		try {
			Criteria criteria = getSession().createCriteria(this.getObjectClass());
	        for (int i = 0; i < propertyNames.length; i++) {
	            if (propertyValues[i] == null) {
	                criteria.add(Restrictions.isNull(propertyNames[i]));                
	            } else {
	                criteria.add(Restrictions.eq(propertyNames[i], propertyValues[i]));                
	            }
	        }
	        return criteria;
		} catch (HibernateException ex) {
			throw new PersistenceExceptionWrapper("Error al crear Criteria", ex, ex.getMessage());
		}
    }
	
	@Override
	public List<T> getByCriteria(DetachedCriteria criteria) throws PersistenceException {
		try {
			getSession().flush();
			return criteria.getExecutableCriteria(getSession()).list();
		} catch (HibernateException ex) {
			throw new PersistenceExceptionWrapper("Error al crear Criteria", ex, ex.getMessage());
		}
	}
	
	@Override
	public List<T> getByCriteria(DetachedCriteria criteria, int amountOfRecords) throws PersistenceException {
		try {
			getSession().flush();
			return criteria.getExecutableCriteria(getSession()).setMaxResults(amountOfRecords).list();
		} catch (HibernateException ex) {
			throw new PersistenceExceptionWrapper("Error al crear Criteria", ex, ex.getMessage());
		}
	}
}
